package com.auditlog.sql.runner;

import cn.hutool.core.collection.CollectionUtil;
import com.auditlog.datasource.StatementProxy;
import com.auditlog.datasource.db.SqlType;
import com.auditlog.datasource.struct.RecordImage;
import com.auditlog.datasource.struct.StatementMetaData;
import com.auditlog.util.IndexUtils;
import net.sf.jsqlparser.statement.insert.Insert;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;


/**
 * @author Zhiyang.Zhang
 * @version 1.0
 * @date 2022/10/30 21:06
 */
public class InsertSqlRunner<T extends java.sql.Statement> extends AbstractInsertSqlRunner<T> {

    public InsertSqlRunner(StatementProxy statementProxy, Insert insert) {
        super(statementProxy, insert);
    }

    private boolean useGeneratedKeys = false;
    private boolean isMultiPkWithoutAutoIncreaseColumn = false;

    /**
     * <b>校验下面几种情况：</b><br></br>
     * &nbsp;&nbsp;1.nonPrepare批处理操作时，oracle和mysql中所有insert必须带有主键值，且不能是sequence<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;1.1 oracle不支持批处理获取产生的key<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;1.2 mysql批处理产生的key无法对应到具体的sql（可能不是insert的同一张表）<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;1.3 mysql插入带select或者多个value，都不能正常获取到产生的key（但是prepare和statement.executeUpdate的可以正常获取到）<br></br><br></br>
     * &nbsp;&nbsp;2.oracle的prepare批处理，所有insert必须带有主键值，且不能是sequence<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;2.1 oracle不支持批处理获取产生的key<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;2.2 mysql的prepaer批处理不要求必须要包含主键，因为mysql可以从产生的key中获取<br></br><br></br>
     * &nbsp;&nbsp;3.oracle处理，对于insert select的必须带有主键值（有的主键是在触发器生成的,所以就没指定），且不能是sequence<br></br>
     * &nbsp;&nbsp;&nbsp;&nbsp;3.1 oracle对于这种不支持获取产生的key<br></br><br></br>
     * &nbsp;&nbsp;4.oracle的prepare和nonPrepare的非批处理，可以不指定主键<br></br><br></br>
     * &nbsp;&nbsp;5.oracle的prepare和nonPrepare的非批处理，如果指定主键，可以是sequence<br></br><br></br>
     * &nbsp;&nbsp;6.其他情况：如果指定了主键，就从插入数据获取，如果没有就getGenerateKey获取<br></br><br></br>
     * &nbsp;&nbsp;7.mysql中values后面跟几个“（）”没关系，但是Oracle本身就会报错<br></br>
     */
    @Override
    public void validate() {
        // 1.
        if (!this.isPrepareStatement() && this.isBatch()) {
            if (!this.insertContainsPk() || this.pkContainsDynamicValue()) {
                throw new IllegalArgumentException("insert batch处理必须指定主键,且不能是动态值.");
            }
        }

        // 2.
        if (this.isPrepareStatement() && this.isBatch() && this.isOracle()) {
            if (!this.insertContainsPk() || this.pkContainsDynamicValue()) {
                throw new IllegalArgumentException("insert batch处理必须指定主键,且不能是动态值.");
            }
        }

        //3.
        if (this.isOracle() && this.getStatement().getSelect() != null) {
            if (!this.insertContainsPk() || this.pkContainsDynamicValue()) {
                throw new IllegalArgumentException("oracle insert select处理必须指定主键,且不能是动态值.");
            }
        }

        // 4. mysql中，当一个sql语句中多条sql时，必须指定主键
        if (this.getStatementProxy().getConnectionProxy().getConnectionContext().isPrepareMultiSql()) {
            if (!this.insertContainsPk() || this.pkContainsDynamicValue()) {
                throw new IllegalArgumentException("多sql语句执行，insert必须指定主键,且不能是动态值.");
            }
        }
    }


    @Override
    public RecordImage beforeImage() throws Exception {
        List<List<Object>> pkValues = this.getPkValues();
        if (CollectionUtil.isEmpty(pkValues)) {
            if (!this.canUseReturnGeneratedKeys()) {
                // 有了前面的校验，这里返回的数据一定带有主键
                pkValues = this.extractPkValues();
            } else {
                if (this.insertMultiPkOnlyWithoutAutoIncrease()) {
                    boolean contains = this.insertContainsAssignedColumns(this.columnsContainsNoAutoIncreasePk);
                    if (!contains) {
                        throw new IllegalArgumentException("插入时,非自增主键必须指定值");
                    } else {
                        isMultiPkWithoutAutoIncreaseColumn = true;
                    }
                }
                useGeneratedKeys = true;
            }
        }
        return RecordImage.builder()
                .insertPks(pkValues)
                .pkColumnsName(this.getTableMeta().getPkNameListWithAlias(this.getAlias()))
                .tableName(this.getTableName())
                .allColumnsWithAlias(this.getTableMeta().getColumnNamesByOrder(this.getAlias()))
                .tableMeta(this.getTableMeta())
                .sqlType(SqlType.INSERT)
                .build();
    }

    @Override
    public RecordImage afterImage(RecordImage beforeRecordImage) throws Exception {
        List<List<Object>> pkValues = beforeRecordImage.getInsertPks();
        if (useGeneratedKeys) {
            ResultSet generatedKeys = this.getStatementProxy().getGeneratedKeys();
            pkValues = this.assembleResult(generatedKeys);
            if (this.isMultiPkWithoutAutoIncreaseColumn) {
                List<String> columnsWithAlias = this.getColumnsWithAlias(this.columnsContainsNoAutoIncreasePk);
                List<Integer> columnWithAliasIndexes = this.getColumnWithAliasIndexes(columnsWithAlias);
                List<List<Object>> otherPkValues = this.getColumnValues(columnWithAliasIndexes);
                if (CollectionUtil.isEmpty(otherPkValues)) {
                    otherPkValues = this.extractColumnValues(columnWithAliasIndexes);
                }
                if (CollectionUtil.isEmpty(otherPkValues)) {
                    throw new IllegalStateException("未找到除自增主键外其他主键的值");
                }
                List<Integer> indexList = IndexUtils.getIndexList(columnsWithAlias, beforeRecordImage.getPkColumnsName());
                int autoIndex = -1;
                for (int i = 1; i <= beforeRecordImage.getPkColumnsName().size(); i++) {
                    if (!indexList.contains(i)) {
                        autoIndex = i;
                        break;
                    }
                }
                if (autoIndex == -1) {
                    throw new IllegalStateException("未找到自增主键的索引");
                }
                List<List<Object>> pkValuesCp = new ArrayList<>();
                for (int i = 0; i < otherPkValues.size(); i++) {
                    List<Object> otherPkValue = otherPkValues.get(i);
                    List<Object> otherPkValueCp = new ArrayList<>();
                    otherPkValueCp.addAll(otherPkValue);
                    Object autoPkValue = pkValues.get(i).get(0);
                    otherPkValueCp.add(autoIndex - 1, autoPkValue);
                    pkValuesCp.add(otherPkValueCp);
                }
                pkValues = pkValuesCp;
            }
            beforeRecordImage.setInsertPks(pkValues);
        }

        if (CollectionUtil.isNotEmpty(pkValues)) {
            this.handleInsertAfterRecordImage(beforeRecordImage);
        }

        return beforeRecordImage;
    }

    @Override
    public StatementMetaData getMetaData(Insert statement) {
        return StatementMetaData.builder()
                .sqlRunner(this)
                .sqlType(SqlType.INSERT)
                .tableName(this.getTableName())
                .build();
    }
}
